#pragma once

#include "ImageEditingUtils.h"



namespace GameGraphics
{
	// Function objects utilizzato per inizializzare l'albero sulla base dell'errore proiettato
	template<typename T>
	struct HMapProjectionBasedErrorParser
	{
		HMapProjectionBasedErrorParser(const size_t iWorldWidth, const size_t iMinCellSize,
			const Ogre::String iHeightMapPath, const float iWorldMaxHeight, const float iTerrainGridStep,
			const float iFov, const size_t iScreenHeight, const float iMinPixelError) :
			mWorldSemiWidth(iWorldWidth/2),
			mMinCellSize(iMinCellSize),
			mTerrainGridStep(iTerrainGridStep),
			mWorldMaxHeight(iWorldMaxHeight),
			mSqrProjConversionFactor(powf(static_cast<float>(iScreenHeight)/(2.0f * iMinPixelError * abs(tanf(iFov * 0.5f))), 2.0f))
		{
			Ogre::TextureManager::getSingleton().load(iHeightMapPath, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D);
			Ogre::TexturePtr hMapTex = Ogre::TextureManager::getSingleton().getByName(iHeightMapPath);
			assert(!hMapTex.isNull());

			hMapTex->convertToImage(mHeightMap);

			mHeightMap.resize(mWorldSemiWidth*2+1, mWorldSemiWidth*2+1, Ogre::Image::FILTER_NEAREST);

			Ogre::TextureManager::getSingleton().unload(iHeightMapPath);
		}



		float RetrieveLowLevelError(const Ogre::Image& iHiResMap)
		{
			assert(iHiResMap.getWidth() == iHiResMap.getHeight());
			assert(Ogre::PixelUtil::getNumElemBytes(iHiResMap.getFormat()) == 1 && "Grayscale only");

			const size_t width = iHiResMap.getWidth();
			const size_t height = iHiResMap.getHeight();

			float maxDiff = 0;

			for(size_t row=0; row<height; ++row)
			{
				for(size_t col=0; col<width; ++col)
				{
					float error = 0;

					if(row%2 != 0)
					{
						if(col%2 != 0) // Average with the four pixels at the corners
						{
							const float mean1 = (iHiResMap.getColourAt(row+1, col+1, 0).r + iHiResMap.getColourAt(row-1, col-1, 0).r)*0.5f;
							const float mean2 = (iHiResMap.getColourAt(row-1, col+1, 0).r + iHiResMap.getColourAt(row+1, col-1, 0).r)*0.5f;
							error = std::max(abs(iHiResMap.getColourAt(row, col, 0).r-mean1), abs(iHiResMap.getColourAt(row, col, 0).r-mean2));
						}
						else // Upper and lower pixel average
						{
							const float mean = (iHiResMap.getColourAt(row+1, col, 0).r + iHiResMap.getColourAt(row-1, col, 0).r)*0.5f;
							error = abs(iHiResMap.getColourAt(row, col, 0).r - mean);
						}
					}
					else if(col%2 != 0) // Previous and next pixel average
					{
						const float mean = (iHiResMap.getColourAt(row, col-1, 0).r + iHiResMap.getColourAt(row, col+1, 0).r)*0.5f;
						error = abs(iHiResMap.getColourAt(row, col, 0).r - mean);
					}

					maxDiff = error > maxDiff ? error : maxDiff;
				}
			}
			return maxDiff;
		}



		void operator()(QuadTree<T>& iTree, const size_t iNodeId, const size_t iTreeLevel)
		{
			QuadTreeNode<T>& node = iTree.GetNode(iNodeId);
			if(iTree.IsLastLevel(iTreeLevel))
			{
				node.mData.error = 0.0f;
				node.mData.minSqrDistance = 0.0f;
				return;
			}

			const size_t currentSemiWidth = mWorldSemiWidth/(0x01U << iTreeLevel);
			const Ogre::Vector2 imageCenter = node.mCenter + static_cast<float>(mWorldSemiWidth);
			const Ogre::Vector2 imageOffset = imageCenter - static_cast<float>(currentSemiWidth);

			Ogre::Image highRes = ImageEditing::CropImage(mHeightMap, static_cast<size_t>(imageOffset.x),
				static_cast<size_t>(imageOffset.y), currentSemiWidth*2+1, currentSemiWidth*2+1);

			for(size_t step = (highRes.getWidth()-1)/2; step > mMinCellSize; step/=2)
			{
				highRes.resize(step + 1, step + 1, Ogre::Image::FILTER_NEAREST);
			}

			const float child0Error = iTree.GetSouthOvestChild(iNodeId).mData.error;
			const float child1Error = iTree.GetSouthEastChild(iNodeId).mData.error;
			const float child2Error = iTree.GetNorthOvestChild(iNodeId).mData.error;
			const float child3Error = iTree.GetNorthEastChild(iNodeId).mData.error;
			const float maxError = std::max(child0Error, std::max(child1Error, std::max(child2Error, child3Error)));
			node.mData.error = RetrieveLowLevelError(highRes) * mWorldMaxHeight + maxError;
			node.mData.minSqrDistance = node.mData.error * node.mData.error * mSqrProjConversionFactor
				+ (currentSemiWidth*mTerrainGridStep)*(currentSemiWidth*mTerrainGridStep);
		}

		Ogre::Image mHeightMap;
		const float mSqrProjConversionFactor;
		const float mWorldMaxHeight;
		const float mTerrainGridStep;
		const size_t mWorldSemiWidth;
		const size_t mMinCellSize;
	};





	// Function objects utilizzato per inizializzare l'albero sulla base della distanza dalla camera
	template<typename T>
	struct HMapDistanceBasedErrorParser
	{
		HMapDistanceBasedErrorParser(const size_t iWorldWidth, const size_t iMinCellSize,
			const float iWorldMaxHeight, const float iTerrainGridStep, const float iDistanceFactor) :
			mWorldSemiWidth(iWorldWidth/2),
			mMinCellSize(iMinCellSize),
			mTerrainGridStep(iTerrainGridStep),
			mWorldMaxHeight(iWorldMaxHeight),
			mDistanceFactor(iDistanceFactor)
		{
		}


		void operator()(QuadTree<T>& iTree, const size_t iNodeId, const size_t iTreeLevel)
		{
			QuadTreeNode<T>& node = iTree.GetNode(iNodeId);
			if(iTree.IsLastLevel(iTreeLevel))
			{
				node.mData.error = 0.0f;
				node.mData.minSqrDistance = 0.0f;
				return;
			}

			const size_t currentSemiWidth = mWorldSemiWidth/(0x01U << iTreeLevel);
			node.mData.error = 0.0f; // Unused
			node.mData.minSqrDistance = mDistanceFactor * (currentSemiWidth+mTerrainGridStep)*(currentSemiWidth+mTerrainGridStep);
		}

		const float mDistanceFactor;
		const float mWorldMaxHeight;
		const float mTerrainGridStep;
		const size_t mWorldSemiWidth;
		const size_t mMinCellSize;
	};
}